/*
 * Copyright (c) 2009 Nathaniel Houghton <nathan@brainwerk.org>
 *                    Robert Kirchgessner <rkirchge@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for
 * any purpose with or without fee is hereby granted, provided that
 * the above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>

#define WINDOWS
 
#include "elm.h"
#include "obd_pid.h"

/*Generic functions*/
uint8_t elm_gets(uint8_t *buf, uint8_t len); /*Reads a string*/
uint8_t elm_readdata( uint8_t *buf, uint8_t len ); /*Read a hex string into a byte array*/
void 	elm_flush( uint8_t ch ); /*Flush till character*/

/*Inline functions*/
inline void elm_set_protocol( uint8_t protocol ); /*Set the protocol with auto search*/

/*Globals*/
uint8_t get_pid_error; /*Error flag. Indicates that there was atleast 1 PID read that had the incorrect value returned*/

/*This function disables echo, sets the protocol, checks to see if it can connect to the bus (and returns 0 if it cannot). It also sets the PID supported values*/
uint8_t 
elm_init( elm_dev *dev, uint8_t prot )
{
	uint8_t buf[25];
	get_pid_error = 0; /*Initialize the error flag to 0*/
	
    elm_setecho(0); /*Turn off echo*/
	elm_set_protocol( prot ); /*Set the protocol*/
	
	snprintf( buf, sizeof(buf), "01 %2.2x 1\r", PID_SUPPORTED ); /*Perform the PID supported check*/
	usart_put( DEV_ELM, buf, sizeof(buf) ); 

	elm_gets( buf, sizeof(buf) ); /*Gets the next line from the buffer.. "SEARCHING"*/	
	elm_gets( buf, sizeof(buf) ); /*Gets the next line from the buffer*/

#ifdef WINDOWS
	printf("Read %s\n", buf);
#endif
	
	if( strcmp( buf, "UNABLE TO CONNECT" ) == 0 ) /*Check to see if we got the could not connect message*/
	{
		status("Unable to connect detected! Bye!\n");
		return 0;
	}
	
	elm_flush('>'); /*Find the prompt*/
	
	elm_getpid( PID_SUPPORTED , buf ,sizeof(buf) ); /*Get the PID again, since last time we just did some error checking.*/
	
	dev->caps[0] = BA4_TO_UINT32(buf); /*Set the first set of device capabilities.*/

	dev->caps[1] = 0x0; /*Set the other lists to 0 before checking if they are supported*/
	dev->caps[2] = 0x0;
	
	if( PID_IS_SUPPORTED( PID_SUPPORTED, PID_SUPPORTED2, dev->caps[0] ) ) /*Check if the second set of PIDs are supported*/
	{
		status("PID SET 2 SUPPORTED. REQUESTING..\n");
		elm_getpid( PID_SUPPORTED2 , buf, sizeof(buf) ); /*Get the PID again, since last time we just did some error checking.*/
		dev->caps[1] = BA4_TO_UINT32(buf); /*Set the first set of device capabilities.*/	
		
		if( PID_IS_SUPPORTED( PID_SUPPORTED2, PID_SUPPORTED3, dev->caps[1] ) ) /*Chek if the third set of PIDs are supported*/
		{
			status("PID SET 3 SUPPORTED. REQUESTING..\n");
			elm_getpid( PID_SUPPORTED3 , buf, sizeof(buf) ); /*Get the PID again, since last time we just did some error checking.*/
			dev->caps[2] = BA4_TO_UINT32(buf); /*Set the first set of device capabilities.*/	
		}
	}
	
	/*Return 1 on success*/
	return 1;
}

/*This function is used to get the value for a PID as a byte array and store it into the buffer. Returns the number of bytes read.*/
/*This function checks that the correct response is returned. If it is not returned, dummy data is returned with the expected number of bytes.*/
uint8_t 
elm_getpid( uint16_t pid, uint8_t *buf, uint8_t len )
{
	uint8_t tmp[10]; /*The minimum number of characters needed*/
	uint8_t read; /*Number of bytes read.*/
	uint8_t expected; /*Number of bytes that should be read*/
	
	snprintf( tmp, sizeof(tmp), "01 %2.2x 1\r", pid ); /*Generate MODE01 request string.*/
	usart_put( DEV_ELM, tmp, sizeof(tmp) ); /*Send the MODE01 request for the PID.*/
	
	elm_readdata( buf, 2 ); /*Read the first two bytes (the tag/PID).*/
	
	expected = PID_LENGTH( pid ); /*Get the expected return length from the PID lookup table.*/
	
	if( (buf[0]<<8 | buf[1]) == ((0x41 << 8)|pid) ) { /*If a correct response header was read.*/
		read = elm_readdata( buf, len );
		
		if( read != expected ) { /*Check that the expected number of bytes were read, if not, return the number of bytes that should have been read.*/
			status("Incorrent number of bytes returned. Returning dummy data.");
			read = expected;
			get_pid_error = 1; /*Set error flag*/
			memset( (void*)buf,0x00, read*sizeof(uint8_t) ); /*Set buffer to 0x00*/
		}
	}
	else /*Incorrect header response*/
	{
		status("Incorrent PID response. Returning dummy data.");
		read = expected;
		get_pid_error = 1; /*Set error flag*/
		memset( (void*)buf,0x00, read*sizeof(uint8_t) ); /*Set buffer to 0x00*/
	}
		
	printf("Get_pid waiting for prmpt\n");
	elm_flush('>'); /*Find the prompt*/
	
	return read;

}

/*This reads a line of hex from the ELM chip, and puts it into a byte array*/
uint8_t 
elm_readdata(uint8_t *buf, uint8_t len)
{
	uint8_t c;
	uint8_t index;
	uint8_t state; /*Used to detect if we are storing the upper or lower nibble*/

	index = 0;
	state = 0;

	while( (c=usart_getc(DEV_ELM)) != '\n' ) {
		
		if( index == len )/*Incase the buffer gets full.*/
			break;
		
		if( isspace(c) )
			continue;
		
		if( c == '\0' )
			continue;

		if( c<='F' && c>='A' ) /*Check if we get a character or not*/
			c = c-'A'+10;
		else
			c = c-'0';

		if(state == 0) { /*Upper Nisbble*/
			buf[index] = c<<4;
			state=1;
		}
		else {		/*Lower Nibble*/		
			buf[index++] += c;
			state=0;
		}
	}

	return index;
}

/*This reads a string from the ELM chip*/
uint8_t 
elm_gets(uint8_t *buf, uint8_t len)
{
	int c;
	int index;
	
	index=0;
	
	while( (c=usart_getc(DEV_ELM)) != '\n')
	{
		if( c == '\0' )
				continue;
		
		buf[ index++ ] = c;
		
		if( index == len - 1)
			break;
	}
	
	buf[ index ] = '\0';
	
	return index;
}

/*This function toggles echo. 0 = ECHO OFF, 1 = ECHO ON.*/
inline void 
elm_setecho( uint8_t val )
{
	uint8_t buf[7];
	
	if(val)
		snprintf( buf, sizeof(buf), "AT E1\r" ); /*Turn echo on*/
	else
		snprintf( buf, sizeof(buf), "AT E0\r" ); /*Turn echo off*/
	
	usart_put( DEV_ELM, buf, sizeof(buf) );
	
	elm_flush('>'); /*Wait for prompt*/

}

/*This function sets which protocol to use.*/
inline void 
elm_set_protocol( uint8_t protocol )
{
	uint8_t buf[10];
	snprintf( buf, sizeof(buf), "AT SP A%1.1x\r", protocol ); /*This searches for the specified prot. If not found, auto searches*/
	usart_put( DEV_ELM, buf, sizeof(buf) ); 
	elm_flush('>'); /*Wait for prompt*/
}

/*This is used to flush until a character, typically the command prompt carrot ">"*/
void elm_flush(uint8_t ch)
{
	uint8_t c;
	status("Flushing...\n");
	
	while( (c=usart_getc(DEV_ELM)) != ch )
		;
	
	usart_getc(DEV_ELM); /*Used in windows to get the uneeded newline character from the buffer*/

}

void elm_reset()
{
	uint8_t buf[] = "AT Z\r";
	usart_put( DEV_ELM, buf, sizeof(buf) ); 
	elm_flush('>'); /*Wait for prompt*/
}
